
/*
 * BlockLinkedList.cpp
 *
 *
 * Created on February 2, 2005, 4:26 PM
 *  Modified on 2/2/08
 *      CGP; abstracted out common base class, BlockLinkedList, for a linked list of blocks. The FreeList and BlockGroup
 *          classes will be extensions of this.
 *  Modified on 1/25/09; CGP; Converted to C++
 *
 */

#include "FSBlock.h"
#include "Util.h"
#include "BlockLinkedList.h"
#include <iostream>
#include <netinet/in.h>
using namespace std;


void BlockLinkedList::DefaultInit() {
    m_disk = NULL;
    m_blockSize = 0;
    m_numberOfBlocks = 0;
    m_startBlockNumber = 0;
    m_endBlockNumber = 0;
    m_seek = NULL;
}

void BlockLinkedList::Init(BlockLinkedList *b) {
	DefaultInit();
    m_disk = b->m_disk;
    m_blockSize = b->m_blockSize;
}


BlockLinkedList::BlockLinkedList() {
    DefaultInit();
}


BlockLinkedList::BlockLinkedList(Disk *disk, unsigned int blockSize) {
    DefaultInit();
    m_disk = disk;
    m_blockSize = blockSize;
    if (blockSize != m_disk->blockSize()) {
        printf("BlockLinkedList::BlockLinkedList: block size" 
            " (%d) is not the same as the disk block size (%d)\n",
            blockSize, m_disk->blockSize());
    }
}


BlockLinkedList::BlockLinkedList(BlockLinkedList *b) {
	Init(b);
}


Disk *BlockLinkedList::GetDisk() {
    return m_disk;
}


bool BlockLinkedList::Initialize(int blockNumber) {
    printf("BlockLinkedList.Initialize: Starting\n");
    
    FSBlock blk(blockNumber, m_disk->blockSize());

    // reset the buffer for this Block-- we don't need it's previous data
    blk.ClearBuffer();

    // Need to initalize the new block next pointer's value to 0-- it will be at the end of
    // the sequence of blocks.
    blk.SetInt(0, 0);

    // Write the updated block to Disk.
    if (! blk.Write(m_disk)) {
        printf("BlockLinkedList.Initialize: ERROR: Cannot write updated block to disk\n");
        return false;
    }

    m_numberOfBlocks = 1;
    m_startBlockNumber = blockNumber;
    m_endBlockNumber = m_startBlockNumber;
    printf("BlockLinkedList.Initialize: Done\n");
    return true;
}


int BlockLinkedList::GetBlockSize() {
    return m_blockSize - sizeof(int); // assume sizeof(int) is 4
}


int BlockLinkedList::GetNumberOfBlocks() {
    return m_numberOfBlocks;
}


FSBlock *BlockLinkedList::UnlinkBlock(
		) {
		
    
    if (m_numberOfBlocks == 0) {
        printf("BlockLinkedList.UnlinkBlock: ERROR: No block to unlink!\n");
        return NULL;
    }

    printf("BlockLinkedList.UnlinkBlock: Starting: startBlockNumber= %d\n", m_startBlockNumber);
    printf("BlockLinkedList.UnlinkBlock: Starting: endBlockNumber= %d\n", m_endBlockNumber);

    FSBlock *blk = new FSBlock(m_startBlockNumber, m_disk);

    m_numberOfBlocks--;

    if (m_numberOfBlocks > 0) {
        // Need to advance the starting block number of this BlockLinkedList
        // to the next block.
        m_startBlockNumber = blk->GetInt(0);

        if (m_startBlockNumber == 0) {
            printf("BlockLinkedList.UnlinkBlock: ERROR: Not at end and startBlockNumber= 0\n");
            delete blk;
            return NULL;
        }
    } else {
        m_startBlockNumber = 0;
        m_endBlockNumber = 0;
    }
    
	// Next pointer of this (new) unlinked block will be 0.
    blk->SetInt(0,0);
    
    // We've modified the next pointer to point to 0. Write the modified block to Disk.
    if (! blk->Write(m_disk)) {
        printf("BlockLinkedList.UnlinkBlock: ERROR: Cannot write updated block to disk\n");
		
        return false;
    }
		
    printf("BlockLinkedList.UnlinkBlock: number of remaining blocks= %d\n", m_numberOfBlocks);
    printf("BlockLinkedList.UnlinkBlock: block number unlinked= %d\n", blk->GetBlockNumber());
    printf("BlockLinkedList.UnlinkBlock: Ending: startBlockNumber= %d\n", m_startBlockNumber);
    printf("BlockLinkedList.UnlinkBlock: Ending: endBlockNumber= %d\n", m_endBlockNumber);
    return blk;
}


bool BlockLinkedList::AddBlock(FSBlock *newBlock
			  ) {
    printf("BlockLinkedList.AddBlock: Starting\n"); fflush(stdout);
    // reset the buffer for this Block-- we don't need it's previous data
    newBlock->ClearBuffer();

    // Need to initalize the new block next pointer's value to 0-- it will be at the end of
    // the sequence of blocks.
    newBlock->SetInt(0, 0);

    // Write the updated block to Disk.
    if (! newBlock->Write(m_disk)) {
        printf("BlockLinkedList.AddBlock: ERROR: Cannot write updated block to disk\n");  fflush(stdout);
        return false;
    }

    // Now, need to modify the block pointer of the block currently at the end of the list.
    printf("BlockLinkedList.AddBlock: Reading ending block number= %d\n", m_endBlockNumber);  fflush(stdout);
    FSBlock *currEndBlock = new FSBlock(m_endBlockNumber, m_disk);

    currEndBlock->SetInt(newBlock->GetBlockNumber(), 0);

    // Write the modified old ending block back to Disk.
    if (! currEndBlock->Write(m_disk)) {
        printf("BlockLinkedList.AddBlock: ERROR: Cannot write updated block to disk\n");  fflush(stdout);
        delete currEndBlock;
        return false;
    }

    if ((m_seek != NULL) && (m_endBlockNumber == m_seek->GetBlockNumber())) {
        delete m_seek;
        m_seek = currEndBlock;
    } else {
        delete currEndBlock;
    }
    
    // Update our ending block number for this BlockGroup.
    m_endBlockNumber = newBlock->GetBlockNumber();

    printf("BlockLinkedList.AddBlock: startBlockNumber= %d\n", m_startBlockNumber);  fflush(stdout);
    printf("BlockLinkedList.AddBlock: endBlockNumber= %d\n", m_endBlockNumber);  fflush(stdout);

    // Increment the number of blocks of this BlockGroup.
    m_numberOfBlocks++;

    printf("BlockLinkedList.AddBlock: Done\n");  fflush(stdout);
    return true;
}


int BlockLinkedList::GetStartBlockNumber() {
    return m_startBlockNumber;
}


int BlockLinkedList::GetEndBlockNumber() {
    return m_endBlockNumber;
}


void BlockLinkedList::Rewind(
        ) {


    //printf("Rewind starts...");
    if (m_seek != NULL) {
        delete m_seek;
        m_seek = NULL;
    }

    if (m_startBlockNumber == 0) {
        return;
    }

    m_seek = new FSBlock(m_startBlockNumber, m_disk);
    //printf("Rewind ends...");
}


FSBlock *BlockLinkedList::GetCurrentBlock() {
    return m_seek;
}


void BlockLinkedList::GetNextBlock(
            ) {
    if (m_seek == NULL) return;

    /* CGP: 3/20/12; The following code had a bug in it. Somehow, I've been allowing the
        m_seek block to reference the master block! Thus, the GetCurrentBlock will be the
        master block!
    */
    //printf("NextBlock: " + m_seek.GetBlockNumber());
    /*
    if (m_seek->GetBlockNumber() == 0) {
        m_seek = NULL;
        return;
    }
    */

    // If this is true, something really crazy has happened, but keep it for
    // robustness:
    if (m_seek->GetBlockNumber() == 0) {  
        printf("BlockLinkedList.GetNextBlock: ERROR: We've got the master block!\n"); 
        return;    
    }

    // Get the block number of the next block in the BlockLinkedList
    int nextBlockNumber = m_seek->GetInt(0);
    delete m_seek;
    if (nextBlockNumber == 0) {
        // There is no next block
        m_seek = NULL; 
    } else {
        //printf("NextBlock: next: " + next);
        m_seek = new FSBlock(nextBlockNumber, m_disk);
    }

    //printf("NextBlock: " + m_seek.GetBlockNumber());
}


void BlockLinkedList::Output() {

    unsigned int numberBlocks = 0; // double check number of Blocks
    
    printf("BlockLinkedList.Output: m_blockSize = %d\n", m_blockSize);
    printf("BlockLinkedList.Output: m_numberOfBlocks = %d\n", m_numberOfBlocks);
    printf("BlockLinkedList.Output: m_startBlockNumber = %d\n", m_startBlockNumber);
    printf("BlockLinkedList.Output: m_endBlockNumber = %d\n", m_endBlockNumber);

    Rewind(
        );

    bool start = true;
    
    // Let's output a newline every 25 blocks
    int lineCount = 1;
    
    for (;;) {
        if ((lineCount % 25) == 0) {
            printf("\n");
        } else if (! start) {
            printf(", ");
        }

        if (GetCurrentBlock() == NULL) break;
        numberBlocks++;

        printf("%d", GetCurrentBlock()->GetBlockNumber());
        //printf(result);

        if (GetCurrentBlock()->GetInt(0) == 0) break;

        GetNextBlock(
            );

        start = false;
        lineCount++;
    }
    
    printf("\n");
    
    if (GetCurrentBlock()->GetBlockNumber() != m_endBlockNumber) {
        printf("BlockLinkedList.Output: ERROR: %d was not the end block number (%d)\n", 
               m_endBlockNumber, GetCurrentBlock()->GetBlockNumber());
    }
    
    if (numberBlocks != m_numberOfBlocks) {
        printf("BlockLinkedList.Output: ERROR: %d was not the number of blocks (%d)\n", 
               m_numberOfBlocks, numberBlocks);
    }

}


bool BlockLinkedList::Replace(FSBlock *blk) {

    /*
    blk->SetPointer(m_seek->GetPointer(0), 0);
    blk->SetBlockNumber(m_seek->GetBlockNumber());
    if (! blk->Write(m_disk)) {
        printf("BlockLinkedList: ERROR: Cannot write to disk\n");
        return false;
    }*/
    unsigned char *dest = m_seek->GetBuffer();
    unsigned char *src = blk->GetBuffer();
    
    for (unsigned int i=sizeof(int); i < blk->GetBlockSize(); i++) {
        dest[i] = src[i];
    }
    
    if (! m_seek->Write(m_disk)) {
        printf("BlockLinkedList.Replace: ERROR: Cannot write to disk\n");
        return false;
    }
    
    return true;
}


int BlockLinkedList::CountBlocks() {

    Rewind(
        );
    int count = 0;

    for (;;) {
        if (GetCurrentBlock() == NULL) break;

        count++;

        if (GetCurrentBlock()->GetInt(0) == 0) break;
        GetNextBlock(
            );
    }

    return count;
}


#define DEVICE_NAME_LENGTH  100

void BlockLinkedList::Test() {
    //char *s = NULL;
    char deviceName[DEVICE_NAME_LENGTH] = "disk1";
    unsigned long numberOfBlocks = 100;
    unsigned int blockSize = Disk::DEFAULT_BLOCK_SIZE;
    int cmd = 0;
    Disk *disk1 = NULL;
    //int blockNumber = 0;
    //int byteToWrite;
    BlockLinkedList *B1;
    bool physicalDevice = false;

    for (;;) {
        printf("\nmain: Enter command:\n");
        printf("   0) Exit program\n");
        printf("   1) Use physical device (default is to use simulated disk in a file)\n"); 
        printf("   2) Open disk object-- named: %s\n", deviceName);
        printf("   3) Low-level reformat disk-- named: %s\n", deviceName);
        printf("   4) Create a new BlockLinked list, B1 with 4 blocks\n");
        printf("   5) Iterate over BlockLinked list, B1\n");
        printf("   6) Output block numbers for BlockLinked list, B1\n");
        printf("   7) Count number of blocks in BlockLinked list, B1\n");
        printf("   8) Unlink first block of BlockLinked list, B1\n");

        printf("Enter command: ");
        ScanfTest("%d", &cmd);

        switch (cmd) {
            case 0:
                return;

            case 1:
                printf("Enter name of physical disk device: ");
                cin.ignore();
                cin.getline(deviceName, DEVICE_NAME_LENGTH);
                physicalDevice = true;
                Disk::GetSizes(deviceName, numberOfBlocks, blockSize);
                break;

            case 2: // create and open disk1
                if (disk1 != NULL) {
                    printf("main: ERROR: disk1 is open!n");
                    break;
                }

                disk1 = new Disk(deviceName, numberOfBlocks, blockSize);
                printf("main: SUCCESS: disk1 is now open.");
                break;

            case 3: // reformat disk1
                if (disk1 == NULL) {
                    printf("main: ERROR: disk1 is not open!\n");
                    break;
                }

                if (disk1->Format()) {
                    printf("main: SUCCESS: formatted disk1!\n");
                } else {
                    printf("main: ERROR: could not format disk1!\n");
                }
                break;

            case 4:
                if (disk1 == NULL) {
                    printf("main: ERROR: disk1 is not open!\n");
                    break;
                }
                {
                    B1 = new BlockLinkedList(disk1, disk1->blockSize());
                    if (B1->Initialize(1)) {
                        printf("SUCCESS: Created list!\n");
                    } else {
                        printf("ERROR: Could not create list!\n");
                    }

                    FSBlock blk(2, disk1->blockSize());
                    printf("Starting to add block 2\n"); fflush(stdout);
                    B1->AddBlock(&blk);
                    printf("Finished adding block 2\n"); fflush(stdout);
                    blk.SetBlockNumber(3);
                    B1->AddBlock(&blk);
                    blk.SetBlockNumber(4);
                    B1->AddBlock(&blk);
                }
                
                break;

            case 5: // iterate over BlockLinkedList B1
                if (disk1 == NULL) {
                    printf("main: ERROR: disk1 is not open!\n");
                    break;
                }

                B1->Rewind();
                while (B1->GetCurrentBlock() != NULL) {
                    printf("Current block number= %d; next pointer= %d\n",
                        B1->GetCurrentBlock()->GetBlockNumber(),
                        B1->GetCurrentBlock()->GetInt(0));
                    if (B1->GetCurrentBlock()->GetInt(0) == 0) break;
                    B1->GetNextBlock();
                }

                break;

            case 6:
                B1->Output();
                break;

            case 7:
                printf("Number of blocks= %d\n", B1->CountBlocks());
                break;

            case 8:
                B1->UnlinkBlock();
                printf("Unlinked first block\n");
                break;

            default:
                printf("main: Bad command!\n");
                break;
        } // end switch
    } // end for
} // end Test()




